package com.stars.easyms.datasource.transaction;

import com.stars.easyms.datasource.EasyMsDataSource;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.AopUtils;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
import org.springframework.transaction.interceptor.*;
import org.springframework.util.ClassUtils;

import java.lang.reflect.Method;
import java.util.Map;

/**
 * <p>className: EasyMsTransactionInterceptor</p>
 * <p>description: EasyMs重写@Transaction注解的拦截器</p>
 *
 * @author guoguifang
 * @date 2019-11-29 19:02
 * @since 1.4.2
 */
@Slf4j
public final class EasyMsTransactionInterceptor implements MethodInterceptor {

    private final EasyMsDataSourceTransactionManager easyMsDataSourceTransactionManager;

    private final TransactionAttributeSource transactionAttributeSource;

    public EasyMsTransactionInterceptor(EasyMsDataSourceTransactionManager easyMsDataSourceTransactionManager) {
        this.easyMsDataSourceTransactionManager = easyMsDataSourceTransactionManager;
        this.transactionAttributeSource = new AnnotationTransactionAttributeSource(false);
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        EasyMsTransactionManager easyMsTransactionManager = createTransactionManager(invocation);
        try {
            Object retVal = proceed(invocation, easyMsTransactionManager);
            commitTransactionAfterReturning(easyMsTransactionManager);
            return retVal;
        } finally {
            cleanupTransactionManager(easyMsTransactionManager);
        }
    }

    private Object proceed(MethodInvocation invocation, EasyMsTransactionManager easyMsTransactionManager) throws Throwable {
        try {
            return invocation.proceed();
        } catch (Throwable throwable) {
            completeTransactionAfterThrowing(easyMsTransactionManager, throwable);
            throw throwable;
        }
    }

    @NonNull
    private EasyMsTransactionManager createTransactionManager(MethodInvocation invocation) {
        // 获取方法对象及方法的类
        Method method = invocation.getMethod();
        Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

        // 获取事务属性及检查事务
        TransactionAttribute txAttr = transactionAttributeSource.getTransactionAttribute(method, targetClass);

        // 创建TransactionInfo对象并记录事务创建日志
        String joinPointIdentification = methodIdentification(method, targetClass, txAttr);
        if (log.isDebugEnabled()) {
            if (txAttr != null) {
                log.debug("Getting transaction for [{}]", joinPointIdentification);
            } else {
                log.debug("Don't need to create transaction for [{}]: This method isn't transactional.", joinPointIdentification);
            }
        }

        return new EasyMsTransactionManager(easyMsDataSourceTransactionManager, txAttr, joinPointIdentification);
    }

    @NonNull
    private String methodIdentification(Method method, @Nullable Class<?> targetClass, @Nullable TransactionAttribute txAttr) {
        String methodIdentification = null;
        if (txAttr instanceof DefaultTransactionAttribute) {
            methodIdentification = ((DefaultTransactionAttribute) txAttr).getDescriptor();
        }
        if (methodIdentification == null) {
            methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass);
        }
        return methodIdentification;
    }

    private void commitTransactionAfterReturning(@NonNull EasyMsTransactionManager easyMsTransactionManager) throws Throwable {
        if (easyMsTransactionManager.hasTransactionStatus()) {
            Map<EasyMsDataSource, EasyMsTransactionStatusHolder> transactionStatusHolderMap = easyMsTransactionManager.getTransactionStatusHolderMap();
            if (!transactionStatusHolderMap.isEmpty()) {
                if (log.isDebugEnabled()) {
                    log.debug("Completing transaction for [{}]", easyMsTransactionManager.getJoinPointIdentification());
                }
                Throwable throwable = easyMsDataSourceTransactionManager.commitOrRollback(transactionStatusHolderMap, !easyMsTransactionManager.isRollbackOnly());
                if (throwable != null) {
                    throw throwable;
                }
            }
        }
    }

    private void completeTransactionAfterThrowing(@NonNull EasyMsTransactionManager easyMsTransactionManager, Throwable throwable) throws Throwable {
        // 获取是否是回滚异常，如果是回滚异常则判断是否是加入事务，如果是加入事务则设置全局回滚
        TransactionAttribute transactionAttribute = easyMsTransactionManager.getTransactionAttribute();
        boolean isRollbackOnThrowable = transactionAttribute != null && transactionAttribute.rollbackOn(throwable);
        if (isRollbackOnThrowable && easyMsTransactionManager.isJoinPreviousTransaction()) {
            easyMsTransactionManager.setRollbackOnly();
        } else if (easyMsTransactionManager.hasTransactionStatus()) {
            Map<EasyMsDataSource, EasyMsTransactionStatusHolder> transactionStatusHolderMap = easyMsTransactionManager.getTransactionStatusHolderMap();
            if (!transactionStatusHolderMap.isEmpty()) {
                if (log.isDebugEnabled()) {
                    log.debug("Completing transaction for [{}] after exception: ", easyMsTransactionManager.getJoinPointIdentification(), throwable);
                }
                boolean isCommit = !isRollbackOnThrowable && !easyMsTransactionManager.isRollbackOnly();
                Throwable t = easyMsDataSourceTransactionManager.commitOrRollback(transactionStatusHolderMap, isCommit);
                if (t != null) {
                    log.error("Application exception overridden by {} exception", isCommit? "commit" : "rollback", throwable);
                    throw t;
                }
            }
        }
    }

    private void cleanupTransactionManager(@NonNull EasyMsTransactionManager easyMsTransactionManager) {
        easyMsTransactionManager.restoreThreadLocalStatus();
    }

}